C++复习 const

Const

  • 定义:const用于定义一个变量,它的值不能被改变

const对象必须初始化

  • 然而并不是所有的const对象都必须初始化

  • 顶层const必须初始化,因为本身不能改变,不初始化根本没法用。而底层const(常量指针)可以不初始化

    参考:https://blog.csdn.net/qq_21034239/article/details/70492318

  • 其实没有那么简单,const对象必须初始化值对bulit-in类型和POD类行有效

    • built-in类型是内置类型包括算术类型和空类型(void)
    • 注意的是指针和引用是复合类型

参考:https://stackoverflow.com/questions/8440381/why-do-const-variables-have-to-be-initialized-right-away
(翻译:https://www.it1352.com/465052.html)

什么是POD类型

参考: 什么是 POD 数据类型?

  • POD类型是 Plain Old Data 的缩写,定义它的作用是为了跟c语言进行兼容,即可以使用 memcpy() 这种最原始的函数进行操作
    • 也就是说,能用 C 的 memcpy() 等函数进行操作的类、结构体就是 POD 类型的数据。

如何判断POD类型

  • 首先可以使用is_pod::value函数进行判断
  • 对于built-in类型肯定是POD类型,而对于类和结构体需要它是trival(平凡)的和布局有序的.

  • trival(平凡)的.
    • 不显式写构造/析构函数、拷贝/移动构造函数、拷贝/移动运算符等
    • 使用delete删除默认的三大函数
    • 使用default声明默认的三大函数
    • 不能有虚函数和虚基类

  • 布局有序
    • 需要有相同的访问级别(private,public)
    • 类中的第一个非静态成员的类型不能与其基类相同
    • 只要有父类,非静态数据只能在其中一个类中,不可分散

const对象仅对当前文件有效

  • 在编译时会进行预处理,将代码中所有带const限定符的变量用初始化的常亮替代。

  • const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同在不同文件中分别定义了独立的变量。

  • 如果要在多个文件中共享const对象,在需要声明变量的时候前面加extern

extern

  • 用于声明
    • 变量只能被定义一次,但可以被多次声明。

链接

  • C/C++程序编译链接过程(还没写)

对const的引用

  • 别称: 常量引用

  • 常量引用可以引用常量和非常量

  • 非常量引用不能引用常量
const int a = 1;
int d = 1;

const int &b = a;//true
const int &c = 1;//true
const int &d = d;//true
int &e = a;//false;
  • 初始化常量引用的时候可以使用任意类型和任意表达式,只要可以转换成相应的引用类型。

    原因:当绑定的常量类型与引用类型不同时,会生成了一个临时变量用于绑定。其事实上绑定的是临时变量。

这样保证了const int&绑定的是int型的表达式

还有值得注意的是一般情况下引用的类型必须与其所引用的对象的类型一致

double dval = 3.14;
const int &ri = dval;

等价于

double dval = 3.14;
const int temp = dval;
const int &ri = temp;
  • 根据上述也可以解释为什么非常量引用不能引用常量,因为这样初始化绑定的是临时量。

    非const变量引用const量,其实绑定了中间变量temp,因为是非const量所以可以改变这非const量,按引用来const量也应该改变,但其实改变的是中间量temp,const量不变,产生矛盾,c++把这种行为定义为非法。

对const的引用可能引用一个并非const的对象

  • const只约束当前绑定的对象的相关操作,而不管对象本身是不是常量。
int a  = 1;
int &b = a;
const int &c = a;
const double &d = a;//注意这里的d绑定的是临时变量temp

b = 2;
cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;
//2 2 2 1

a = 3;
cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;
//3 3 3 1

c = 4;
cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;
// error: assignment of read-only reference ‘c’

指针和const

对于指针常量和常量指针的定义C++ primer里定义的跟大多数教材不一样

指针常量(pointer to const)

  • 从右往左读,它是一个指针,是一个指向常量类型的指针。说明不能改变他所指向对象的内容。但是可以改变它指向的对象。
const int a = 1;
const int b = 2;
const int *c = &a;
*c = 1;//fasle
c = &b;//true

常量指针(const pointer)

  • 从右往左读,它是一个常量,说明指针的地址不能改变。但是可以改变它指向对象的内容。
int a = 1;
int b = 2;
int *const c = &a; 
c= &b;//false
c= 2;//true

顶层const和底层const

  • 顶层const说明指针本身是个常量-常量指针(const pointer)
  • 底层const说明指针指向的对象是个常量-指针常量(pointer to const)

const对拷贝的影响

  • 顶层const对拷贝无影响
  • 底层const对拷贝有影像

    拷贝两者有相同的底层const的,或者被拷贝数据可以从非常量转变为常量,才无影响

int main()
{   
// 顶层const对拷贝无影响
    {
        // const int -> int  yes
        const int b = 1;
        int a = b;

        // int -> const int yes
        int c = 1;
        const int d = c;
    } 

    // &int(int*) -> int *const/const int*/const int *const
    int a = 1;
    int *const b = &a; //ok
    const int *c = &a; //ok
    const int *const d = &a; //ok

    // &(const int) -> int *const/const int*/const int *const
    const int e = 1;
    int *const f = &e; //error 没有相同的底层const
    const int *g = &e; //ok
    const int *const h = &e; //ok  &e可以转换成const int *const

    //(int cont*) -> int *const/const int*/const int *const
    int i = 1;
    int *const  j= &i;
    int *const  k = j; //ok
    const int*  l = j; //ok
    const int *const m = j; //ok

    return 0;
}
  • 总结(我自己有点绕晕了,总结一下)

    1) 对于非指针类型之间的拷贝,没有限制 const int <-> int

2) 对于指针之间的拷贝。右值没有底层const,总合法;右值有底层const,则看左值有没有底层const,有则合法,没有则不合法。

  • 常量对象不能赋值给非常量引用,常量对象不能赋值给非常量指针

3) 对于指针和非指针之间,类型不同不合法;

constexpr和常量表达式[c++11]

  • 常量表达式(const expression)是指不会改变并且在编译过程就能得到计算结果的表达式

    数据类型是常量并且初始值是常量或字面值或两者混合的是常量表达式。

const int a = 1; //true
const int b = a+1; //true
int c = 1; //false
const int d = get_d(); //false,该值在编译时才能获得,所以不是常量表达式

constexpr变量

  • constexpr定义的变量必须要用常量表达式初始化
  • constexpr是顶层const,即其本身为常量
  • 对于字面量

    算术类型,引用和指针,字面值常量类(包括枚举类型),枚举都是字面值
    自定义类,IO库,string类等不算字面值

constexpr 指针

  • constexpr指针只能指向地址固定的变量,比如全局变量,staitc变量
constexpr int *p1; //顶层const
const int *p2;  //底层const
int *const p3;  //顶层const

constexpr const int *p4;
//<=>
const int *const p5;

const补充

const 使用

  • 修饰变量,说明该变量不可以被改变;
  • 修饰指针,分为指向常量的指针(pointer to const)和自身是常量的指针(常量指针,const pointer);
  • 修饰引用,指向常量的引用(reference to const),用于形参类型,即避免了拷贝,又避免了函数对值的修改;
  • 修饰成员函数,说明该成员函数内不能修改成员变量
    • 注意常量类型只能调用常量成员函数
    • 非常量类型能调用常量成员函数和非常量成员函数
// 类
class A
{
private:
    const int a;                // 常对象成员,只能在初始化列表赋值

public:
    // 构造函数
    A() : a(0) { };
    A(int x) : a(x) { };        // 初始化列表

    // const可用于对重载函数的区分
    int getValue();             // 普通成员函数
    int getValue() const;       // 常成员函数,不得修改类中的任何数据成员的值
};

void function()
{
    // 对象
    A b;                        // 普通对象,可以调用全部成员函数、更新常成员变量
    const A a;                  // 常对象,只能调用常成员函数
    const A *p = &a;            // 指针变量,指向常对象
    const A &q = a;             // 指向常对象的引用

    // 指针
    char greeting[] = "Hello";
    char* p1 = greeting;                // 指针变量,指向字符数组变量
    const char* p2 = greeting;          // 指针变量,指向字符数组常量(const 后面是 char,说明指向的字符(char)不可改变)
    char* const p3 = greeting;          // 自身是常量的指针,指向字符数组变量(const 后面是 p3,说明 p3 指针自身不可改变)
    const char* const p4 = greeting;    // 自身是常量的指针,指向字符数组常量
}

// 函数
void function1(const int Var);           // 传递过来的参数在函数内不可变
void function2(const char* Var);         // 参数指针所指内容为常量
void function3(char* const Var);         // 参数指针为常量
void function4(const int& Var);          // 引用参数在函数内为常量

// 函数返回值
const int function5();      // 返回一个常数
const int* function6();     // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7();     // 返回一个指向变量的常指针,使用:int* const p = function7();